#!coding:utf-8

"""
A connection to a virtualbox through vboxapi.

"""
import multiprocessing
import os,shutil
import platform
import psutil
import uuid
import logging
import socket, struct
import tarfile
import json
from multiprocessing import Process 

from vboxapi import VirtualBoxManager
from eventlet import greenthread
import eventlet
import datetime

from tcloudhd.vbox import vbox_states,vm_utils
from tcloudhd.vbox.vbox_states import vbox_power_state
from tcloudhd.common import exception,constant,utils
from tcloudhd.manager.models import Instance,InstanceMedium, Template, Snapshot, Server

LOG = logging.getLogger('thor.agent.service')



class redirect(object):
    def __init__(self,constants,name,proto,hostip,hostport,guestport):
        self.name = name
        if proto == 'udp':
            self.proto = constants.NATProtocol_UDP
        else:
            self.proto = constants.NATProtocol_TCP
        self.hostip = hostip
        self.hostport = hostport
        self.guestport = guestport
    
    def get_s(self,guestip):
        return "%s,%s,%s,%s,%s,%s" % \
            (self.name,self.proto,self.hostip,self.hostport,guestip,self.guestport)

class VboxSession(object):
    '''
    context with auto unlock session
    '''
    def __init__(self,vbm,instance,needsave=True, permitSharing=True):
        self.vbm = vbm
        self.needsave = needsave
        self.permitSharing = permitSharing
        if isinstance(instance,str):
            name = instance
        else:
            name = instance.name
        self.mach = vbm.vbox.findMachine(name)
        
    def __enter__(self):
        try:
            self.session = self.vbm.openMachineSession(self.mach, self.permitSharing)
        except:
            raise exception.InstanceLockFailed('lock instance %s' % self.mach.name)
        
        return self.session
        
    def __exit__(self, exc_type, exc_value, traceback):
        try:
            if self.needsave:
                self.session.machine.saveSettings()
            self.session.unlockMachine()
        except Exception as e:
            LOG.exception(e)
        
        
class VboxService(object):

    def __init__(self, conf,style=None,params=None):
        self.conf = conf
        self.vbm = VirtualBoxManager(style, params)
        self.vbox = self.vbm.vbox
        self.host = self.conf['agent.host']
        self.tcloud_path = self.conf['agent.default_machine_folder']
        self.network_class = self.conf['agent.network.class']
        self.use_iscsi = False
        self.block_migration_port = utils.generate_port(\
                                        self.conf.get('agent.blockmigration.port'))
        self.teleport_port = utils.generate_port(\
                                self.conf.get('agent.teleport.port'))

        self.bridgeIF = self.conf.get('agent.bridged_if')
        assert self.host
        assert self.tcloud_path

        #assert self.use_iscsi
        
        self.vbox.systemProperties.defaultMachineFolder = self.tcloud_path
        self.pool = eventlet.GreenPool(128)
        
        import platform 
        bit, _ = platform.architecture()
        self.is_x86_64 = -1 != bit.find('64bit') 


    def _open_medium(self, instance_medium):

        if not os.path.exists(instance_medium.location):
            return False

        if instance_medium.parent_uuid:
            self._open_medium(instance_medium.parent_uuid)
        try:
            try:
                self.vbox.findMedium(instance_medium.location,
                                     self.vbm.constants.DeviceType_HardDisk)
                return True
            except Exception:
                self.vbox.openMedium(instance_medium.location,
                                     self.vbm.constants.DeviceType_HardDisk,
                                     self.vbm.constants.AccessMode_ReadOnly,
                                     False)
        except Exception:
            raise exception.InstanceMediumNotExist(location=instance_medium.location)
    
    def _prepare_mediums(self,instances):
        """
        For reboot system when using nfs, the mediums need to be registered first
        """
        clean_instances = []
        for inst in instances:

            try:
                #self._cache_image_hdd(inst.image,inst.mount_point)
                #TODO 是否会激活所有的磁盘
                instance_medium = InstanceMedium.objects.get_or_none(instance_id=inst.id,is_root_dev=True)
                if instance_medium:
                    result = self._open_medium(instance_medium)
                    if not result:
                        instance_medium.remove()
            except:
                LOG.exception("Can not prepare medium %s for instance %s.") % (instance_medium.location, inst.name)
                utils.die("prepare mediums failed")

            #虚拟机setting 文件被删除了，我们看作这台虚拟机不存在了
            setting_path = os.path.join(inst.mount_point, inst.name,
                                        '%s.vbox' % inst.name)
            if not os.path.exists(setting_path):
                inst.remove()
                continue

            try:
                self.vbox.findMachine(inst.uuid)
                clean_instances.append(inst)
            except:
                mach = self.vbox.openMachine(setting_path)
                try:
                    self.vbox.registerMachine(mach)
                    clean_instances.append(inst)
                except Exception as e:
                    LOG.error(e)
                    utils.die("Can not open setting path %s" % setting_path)

        # since VirtualBoxManage is in the current process and thread,
        # here need to reload machines
        os.system('vboxmanage list vms')
        return clean_instances

    def init_host(self):
        '''
        1.agent启动时要恢复running的instance，以及instance相关的规则
        2.如果volume drive使用nfs方式，启动则需要挂载nfs
        '''


        host = Server.objects.get_or_none(ip=self.host)
        if not host:
            Server.objects.create(name=utils.get_host_name(),
                                  ip=self.host, cpu=multiprocessing.cpu_count(),
                                  mem=utils.get_memory_mb_total(),
                                  arch=platform.machine(), cpu_type=utils.get_cpu_type(),
                                  cpu_usage=psutil.cpu_percent(),
                                  mem_usage=psutil.phymem_usage().percent, os=platform.uname(), status=2)


        instances = Instance.objects.filter(host=host)
        # @TODO storage mount
        # if iscsi

        clean_instances = self._prepare_mediums(instances)

        for index,instance in enumerate(clean_instances):
            db_state = instance.status
            LOG.debug('Checking state of instance %s......' % instance.name)
            try:
                drv_state =  self.get_info(instance.name)[0]
            except exception.NotFound:
                drv_state = vbox_states.ERROR

            LOG.debug('Current state is %(drv_state)s, state in DB is '
                      '%(db_state)s.' % locals())

            if db_state == vbox_power_state[drv_state]:
                continue
            else:
                if db_state == vbox_states.RUNNING:
                    try:
                        self.start(instance)
                    except:
                        self._update_instance_state(instance,vbox_states.ERROR)
                        LOG.error("error in start instance %s " % instance['name'])

                elif (db_state == vbox_states.SHUTDOWN
                      and drv_state == vbox_states.RUNNING):
                    try:
                        self.shutdown(instance.name)
                    except:
                        self._update_instance_state(instance,vbox_states.ERROR)
                        LOG.error("error in shutdown instance %s " % instance['name'])
                else:
                    self._update_instance_state(instance, vbox_power_state[drv_state])


    def get_info(self, instance_name):
        """Get the current status of an instance, by name (not ID!)

        Returns a dict containing:

        :state:           the running state, one of the power_state codes
        :max_mem:         (int) the maximum memory in KBytes allowed
        :mem:             (int) the memory in KBytes used by the domain
        :num_cpu:         (int) the number of virtual CPUs for the domain
        :cpu_time:        (int) the CPU time used in nanoseconds
        """
        mach = self.vbox.findMachine(instance_name)
        
        if mach.state != self.vbm.constants.MachineState_Running:
            return [mach.state,0,0,0,0]
        
        with VboxSession(self.vbm,instance_name) as session:
            stats = session.console.guest.internalGetStatistics()
        return [mach.state,mach.memorySize*1024,stats[3]-stats[4],mach.CPUCount,0]
        

    def list_instances(self):
        """
        Return the names of all the instances known to the virtualization
        layer, as a list.
        """
        return [name for name,state in self.list_instances_detail()]
        
    def list_instances_detail(self):
        """Return a list of InstanceInfo for all registered VMs"""
        return [(m.name,vbox_power_state[m.state]) 
                for m in self.vbm.getArray(self.vbox, 'machines')]
    
    def _update_instance_state(self, instance, state):
        inst_ref = instance.update(dict(status=state))
        inst_ref.save()
        return inst_ref
    
    def _allowed_instances(self, instance):
        vcpu = instance['vcpu']
        memory = instance['memory']
        if int(vcpu) > 2 * self.vbox.host.processorCount:
            raise exception.InstanceProcessorInvalid(cpu_core=self.vbox.host.processorCount)
        
        if int(memory) > self.vbox.host.memoryAvailable:
            raise exception.InstanceMemoryInvalid(memory_exist=self.vbox.host.memoryAvailable,
                                                  memory_required=memory)

    def _clean_hds(self,instance,mach,cleanup=True):
        clean_mode = self.vbm.constants.CleanupMode_DetachAllReturnNone
        if cleanup:
            clean_mode = self.vbm.constants.CleanupMode_DetachAllReturnHardDisksOnly

        hds = mach.unregister(clean_mode)
        unregister_hds = []
        if hds:
            primary_img = InstanceMedium.objects.filter(instance_id=instance.id,is_root_dev=True)
            if not primary_img:
                LOG.warning('instance medium disappeared unexpectedly')
                return hds

            for hd in hds:
                if hd.uuid == primary_img.uuid and hd.getChildren():
                    unregister_hds.append(hd)
                if not hd.parent:unregister_hds.append(hd)
            progress = mach.delete_config(hds)
            LOG.info('cleanup hardDisk %s' % instance.name)
            progress.waitForCompletion(-1)

        return unregister_hds

    def _clean_vms(self,instance,hds=None):
        instance.remove()
        target = os.path.join(instance.mount_point,instance.name)
        LOG.info('Instance %s: cleanup instance files %s' % (instance.name,
                                                              target))
        if hds:
            for unregister_hd in hds:
                self._deep_unregister_medium(unregister_hd)

        if os.path.exists(target):
            shutil.rmtree(target)

    def _get_machine(self, instance_name):

        instance_ref = Instance.objects.get_or_none(name=instance_name)
        if not instance_ref:
            raise exception.InstanceNotFoundInDB(name=instance_name)
        try:
            mach = self.vbox.findMachine(instance_ref.uuid)
        except:
            raise exception.InstanceNotFoundInHost(name=instance_ref.name,
                                                   host=self.host)
        return instance_ref, mach

    """
        vdisk image 如果有base则从base那里创建diff
        @params instance_id
        @params image
        @params disk

    """
    def create_VDisk(self,instance,image,disk):

        # 如果有存在的disk,则直接打开，这个时候应该是有
        try:
            primary_hdd = self.vbox.openMedium(disk['location'],
                                            self.vbm.constants.DeviceType_HardDisk,
                                            self.vbm.constants.AccessMode_ReadWrite,
                                            False)
        except:
            LOG.warning("open %s medium fail, we are going to create a new one." % disk['location'])
            primary_hdd = self.vbox.createHardDisk('vdi', disk['loc'
                                                               'ation'])
            #如果有模版, 如果是地一块硬盘,则创建 diff
            if image['has_base'] and disk['is_root']:
                base_hdd = self._cache_image_hdd(image,instance.mount_point)
                progress = base_hdd.createDiffStorage(primary_hdd,
                                                      self.vbm.constants.MediumVariant_Diff)
                progress.waitForCompletion(-1)
                instance_medium = InstanceMedium(uuid=primary_hdd.id,
                                                 parent_uuid=base_hdd.id,
                                                 is_root_dev= True,
                                                 location = disk['location'],
                                                 size=disk['size'],
                                                 instance_id=instance.id)
            else:
                progress = primary_hdd.createBaseStorage(disk['size'] * constant.TO_GB,
                                                         self.vbm.constants.MediumVariant_Standard)
                progress.waitForCompletion(-1)
                instance_medium = InstanceMedium(uuid=primary_hdd.id,
                                                 is_root_dev = False,
                                                 location = disk['location'],
                                                 size=disk['size'],
                                                 instance_id=instance.id)
            instance_medium.save()

        return primary_hdd

    def _prepare_disk(self,instance,image,disks):

        i = 0
        for disk in disks:
            if disk['disk_type'] == constant.DEVICE_DISK:

                disk['location'] = os.path.join(instance.mount_point,
                                               instance.name,
                                               '%s.disk_%s' % (instance.name,i))
                try:
                    with VboxSession(self.vbm,instance) as session:
                        mach = session.machine
                        hdd = self.create_VDisk(instance,image,disk)
                        # 创建控制器
                        storage_ctl = mach.addStorageController(constant.SATA,
                                                                self.vbm.constants.StorageBus_SATA)
                        storage_ctl.portCount = constant.MAX_PORT_COUNT
                        # 挂在
                        mach.attachDevice(constant.SATA, constant.DEFAULT_PORT + i, constant.SOLT_MASTER,
                                          self.vbm.constants.DeviceType_HardDisk, hdd)
                except:
                    LOG.exception('Exception in attaching image')
                    raise exception.InstanceAttachFailure()
                i = i + 1
            elif disk['disk_type'] == constant.DEVICE_ISO:
                assert disk['location']
                try:
                    with VboxSession(self.vbm,instance) as session:
                        mach = session.machine
                        cdrom = self.vbox.openMedium(disk['location'],
                                                   self.vbm.constants.DeviceType_DVD,
                                                   self.vbm.constants.AccessMode_ReadOnly,
                                                   False)
                        cdrom_ctl = mach.addStorageController(constant.IDE,
                                                                self.vbm.constants.StorageBus_IDE)
                        cdrom_ctl.portCount = 2
                        # 挂在
                        mach.attachDevice(constant.IDE, constant.DEFAULT_PORT + i, constant.SOLT_MASTER,
                                          self.vbm.constants.DeviceType_DVD, cdrom)
                except:
                    LOG.exception('Exception in attaching image')
                    raise exception.InstanceAttachFailure()
                i = i + 1

    def _prepare_image(self,image):

        if 'id' in image:
            image_id = image.id
            template = Template.objects.get_or_none(name=image_id)
            if not template:
                raise exception.ImageNotExist(image_id=image_id)
            template['has_base'] = True
        else:
            template = image
            template['has_base'] = False

        return template

    def _check_instance_name(self,instance):
        assert instance['name']
        if instance['name']:
            vmref = self._get_vm_from_name(instance['name'])
            if vmref:
                raise exception.InstanceExists(name=instance['name'])


    def create_vm(self,instance,image,disks):
        """
        Create a new instance/VM/domain on the virtualization platform.
        *create machine
        *set config of machine
        *add storage controller
        *register image(VDI)
        *attach Device
        *attch local gb
        *set up network
        :param instance: Instance object as returned by DB layer.
                         This function should use the data there to guide
                         the creation of the new instance.
        :param image_id: imageid passed in
        """

        self._check_instance_name(instance)
        self._allowed_instances(instance)
        template = self._prepare_image(image)

        try:
            host = Server.objects.get_or_none(ip=instance['host'])

            inst_ref = Instance(vcpu=instance['vcpu'],
                                 image_id= template['id'] if template['has_base'] else None,
                                 memory=instance['memory'],
                                 host_id=host.id,
                                 status=vbox_states.REGISTERING,
                                 name=instance['name'],
                                 os_type=template['os_type'],
                                 mac_address=utils.generate_mac(),
                                 storage_id = instance['storage_id'])
            inst_ref.save()

            #TODO 网络设置
            #self.allocate_fixed_ip(instance, address)  #分配ip,必须最先调用,如果没有可用的ip，则中断

            location = os.path.join(inst_ref.mount_point,
                                    inst_ref.name)
            vm_utils.ensure_fold(location)
            setting_file = location + "/" + inst_ref.name + '.vbox'

            mach = self.vbox.createMachine(setting_file,
                                           inst_ref['name'],
                                           template['os_type'],
                                           constant.EMPTY_UUID, False)
            self._ajust_vm_config(mach, inst_ref)
            inst_ref.update(dict(uuid=mach.id))
            
            self.vbox.registerMachine(mach)
            self._update_instance_state(inst_ref, vbox_states.REGISTERED)
            self._prepare_disk(inst_ref,template,disks)
            self._update_instance_state(inst_ref,vbox_states.SHUTDOWN)

        except:
            LOG.exception("Register instance failed")
            self._clean_hds(inst_ref,mach)
            self._clean_vms(inst_ref)
            raise exception.InstanceRegisterFailure()

        if 'is_spawn' in instance and instance['is_spawn']:
            self.start(instance)

        return instance

    def _cache_image_hdd(self,image,mount_point):
        """
        image cache for host
        :param image: image object
        """
        cache_image = vm_utils.cache_base_image(mount_point,image)

        try:
            base_hdd = self.vbox.openMedium(cache_image,
                                 self.vbm.constants.DeviceType_HardDisk,
                                 self.vbm.constants.AccessMode_ReadWrite,
                                 False)
            if not image.uuid:
                image.uuid = base_hdd.id
            if image.uuid != base_hdd.id:
                raise exception.ImageInvalid(image_id=image.imageid)
            image.save()
        except exception.ImageInvalid as invalid:
            raise invalid
        except Exception as e:
            LOG.exception('Open base image failed: %s' % e)
            raise exception.FetchImageError(image_loc=cache_image)
        
        if base_hdd.type != self.vbm.constants.MediumType_MultiAttach:
            base_hdd.type = self.vbm.constants.MediumType_MultiAttach
            
        db_base_medium = InstanceMedium.objects.get_or_none(uuid=base_hdd.id)
        if not db_base_medium:
            instance_medium = InstanceMedium(uuid=base_hdd.id,
                                             size = base_hdd.size,
                                             location = cache_image,
                                             is_template=True)
            instance_medium.save()

        return base_hdd
    
    def start(self, instance_name, run_type='sdl'):
        """
        Start instance
        :param instance: instance which provide the primary key
        :param runtype: vbox running type, default to headless
        """
        instance, mach = self._get_machine(instance_name)
        
        self._allowed_instances(instance)
        
        if mach.state == self.vbm.constants.MachineState_Running:
            LOG.info('Instance %s is already running' % instance.name)
            return instance

        try:
            #TODO
            #self.bind_fixed_ip(instance) #先绑定ip，ip生效需要一点时间
            
            session = self.vbm.mgr.getSessionObject(self.vbox)
            LOG.info('Starting the instance %s with run type %s' % 
                      (instance.name, run_type))
            progress = mach.launchVMProcess(session, run_type, "")
            progress.waitForCompletion(-1)
            session.unlockMachine()
            
            LOG.debug('Complete starting the instance %s' % instance.name)
            
            #self.fwdriver.apply_instance_filter(instance) #iptables,这里是加入，所以不使用refresh
            
            #self.apply_redirect(instance) #port forwarding

            instance.launched_at = datetime.datetime.now()
            self._update_instance_state(instance, vbox_states.RUNNING)

            return True

        except Exception as e:
            LOG.exception('Start instance %s failed as exception %s' % 
                          (instance.name, e))
            raise exception.InstanceStartFailure(name=instance.name)
        
    def _ajust_vm_config(self, mach, instance):
        """
        memory_mb,vcpus,network
        """
        mach.CPUCount = instance.vcpu
        mach.memorySize = instance.memory

        # would test this
        if mach.OSTypeId.startswith('Windows') or mach.CPUCount>1:
            mach.BIOSSettings.IOAPICEnabled = True

        # usb
        mach.USBController.enabled = False
        mach.USBController.enabledEhci = False
        
        # some base initializing
        mach.VRAMSize = 64
        
        #服务器不能开启3d加速
        mach.accelerate3DEnabled = self.vbox.host.Acceleration3DAvailable
        mach.accelerate2DVideoEnabled = True
        
        # audio
        mach.audioAdapter.enabled = utils.hasSoundCard()
        # Intel HD Audio support was added with VirtualBox 4.0 because Windows 7 (32-bit and 64-bit versions) 
        # as well as 64-bit Windows Vista do not support the Intel AC’97 controller.
        mach.audioAdapter.audioController = self.vbm.constants.AudioControllerType_AC97
        if mach.OSTypeId.startswith('Windows7') or mach.OSTypeId == 'WindowsVista_64':
            mach.audioAdapter.audioController = self.vbm.constants.AudioControllerType_HDA
        # according to the VBox User Manual(3.7)
        # On newer Linux distributions (Fedora 8 and above, Ubuntu 8.04 and above) 
        # the PulseAudio subsystem should be preferred. 
        mach.audioAdapter.audioDriver = self.vbm.constants.AudioDriverType_ALSA
        
        #boot order
        mach.setBootOrder(1, self.vbm.constants.DeviceType_HardDisk)
        mach.setBootOrder(2, self.vbm.constants.DeviceType_DVD)
        mach.setBootOrder(3, self.vbm.constants.DeviceType_Null)
        mach.setBootOrder(4, self.vbm.constants.DeviceType_Null)
        
        # rdp
        # 我们不需要vrdp
        
        #network
        #TODO 网络修改
        networkAdapter = mach.getNetworkAdapter(0)  #网卡
        if not networkAdapter.enabled:
            networkAdapter.enabled = True
        networkAdapter.attachmentType = self.vbm.constants.NetworkAttachmentType_Bridged
        #根据测试结果，virtio类型的网卡速度最优
        # virtio 需要安装驱动
        networkAdapter.adapterType = self.vbm.constants.NetworkAdapterType_I82540EM
        networkAdapter.MACAddress = instance.mac_address
        networkAdapter.bridgedInterface = self.bridgeIF

        # VBox User Manual(4.8.2), it avoids memory duplication between several 
        # similar running VMs for with identical operating systems.
        # (4.8.1), memory balloon can share memory between vms

        #这里共享内存会有内存泄露，建议关闭
        # support 64bit
        if self.is_x86_64:
            mach.pageFusionEnabled = True
            balloon_size = int(0.2 * mach.memorySize)
            mach.memoryBalloonSize = balloon_size
            mach.setExtraData('VBoxInternal/Guest/BalloonSizeMax', str(balloon_size))
#            this work with one of below setting  
#            VBoxManage setextradata global VBoxInternal/Guest/BalloonSizeMax <Size in MB>
#            VBoxManage setextradata "VM-Name" VBoxInternal/Guest/BalloonSizeMax <Size in MB>
        
        
    def _get_vm_from_name(self,name):
        try:
            mach = self.vbox.findMachine(name)
            return mach
        except:
            return None

    def destroy(self, instance_name, cleanup=True, unallocate_fixed_ip=True):
        """Destroy (shutdown and delete) the specified instance.

        1.if vm start up then shutdown
        2.unregister vm
        3.unregister hd
        4.delete hd

        :param instance: Instance object as returned by DB layer.
        :param cleanup:

        """
        instance, mach = self._get_machine(instance_name)
        
        # if machine is in below state, shutdown first
        if mach.state in [self.vbm.constants.MachineState_Running,
                          self.vbm.constants.MachineState_Paused,
                          self.vbm.constants.MachineState_Stuck]:
            self.shutdown(instance) 
            self._wait_for_unlock(mach)

        try:
            unregister_hds = self._clean_hds(instance,mach,cleanup)

            LOG.info('unregister the instance %s' % instance.name)
        except Exception as e:
            self._update_instance_state(instance, vbox_states.ERROR)
            LOG.critical("Destroy instance %s failed as exception %s" % 
                          (instance.name, e),  exc_info=True)
            raise exception.InstanceDestroyFailure(name=instance.name)

        if cleanup:
            instance.status = vbox_states.DELETED
            instance.remove()

            # delete snapshots
            snapshots = Snapshot.objects.filter(instance=instance)
            if snapshots:
                # [snapshot.delete() for snapshot in snapshots]
                snapshots.delete()

            # delete instance mediums
            instance_mediums = InstanceMedium.objects.filter(instance=instance)
            if instance_mediums:
                [instance_medium.delete() for instance_medium in instance_mediums]

            LOG.info('Complete destroy the instance %s' % instance.name)

            self._clean_vms(instance=instance,hds=unregister_hds)

    def _deep_unregister_medium(self, unregister_hd):
        """
        When fully destroy the unregister hdds, lots of them are not truely 
        removed, so here done.
        """
        try:
            LOG.info("Unregister hd location: %s." % unregister_hd.location)
            if unregister_hd.getChildren():
                for child in unregister_hd.getChildren():
                    self._deep_unregister_medium(child)
            unregister_hd.deleteStorage()
        except Exception as e:
            LOG.warn("Removing unregister failed %s." % e)
        
            
    #关闭instance后，并不是及时解锁，所以我们需要等待
    def _wait_for_unlock(self,mach):
        while 1:
            if mach.sessionState == self.vbm.constants.SessionState_Unlocked:
                break
            greenthread.sleep(0.1)
            
            
    def shutdown(self,instance_name):
        instance, mach = self._get_machine(instance_name)
        if mach.state not in [self.vbm.constants.MachineState_Running,
                              self.vbm.constants.MachineState_Paused,
                              self.vbm.constants.MachineState_Stuck]:
            raise exception.InstanceShutdownRequired(name=instance.name)
            
        try:
            LOG.info('Shutdowning the instance %s' % instance.name)
            with VboxSession(self.vbm, instance, False) as session:
                process = session.console.powerDown()
                process.waitForCompletion(-1)

            
            instance = self._update_instance_state(instance, vbox_states.SHUTDOWN)
            LOG.info('Complete shutdowning the instance %s' % instance.name)
            return instance
        except Exception as e:
            self._update_instance_state(instance, vbox_states.ERROR)
            LOG.exception("Shutdown instance %s failed as exception %s" % 
                          (instance.name, e))
            raise exception.InstanceShutdownFailure(name=instance.name)
            
            
    def reboot(self, instance_name):
        """Reboot the specified instance.

        :param instance: Instance object as returned by DB layer.
        """
        instance, mach = self._get_machine(instance_name)
        
        try:
            LOG.info('Rebooting the instance %s' % instance.name)
            self.shutdown(instance)
            
            self._wait_for_unlock(mach)
            
            instance = self.start(instance)
            LOG.info('Complete rebooting the instance %s' % instance.name)
            return instance
        except Exception as e:
            self._update_instance_state(instance, vbox_states.ERROR)
            LOG.exception("Reboot instance %s failed as exception %s" %
                          (instance.name, e))
            raise exception.InstanceRebootFailure(name=instance.name)
    
    def pause(self, instance_name):
        """
        Pause the specified instance.
        """
        instance, mach = self._get_machine(instance_name)
        if mach.state != self.vbm.constants.MachineState_Running:
            raise exception.InstancePauseRequired(name=instance.name)
        try:
            LOG.info('Pausing the instance %s' % instance.name)
            with VboxSession(self.vbm, instance, False) as session:
                session.console.pause()
            instance = self._update_instance_state(instance, vbox_states.PAUSED)
            LOG.info('Complete pausing the instance %s' % instance.name)
            return instance
        except Exception as e:
            self._update_instance_state(instance, vbox_states.ERROR)
            LOG.exception("Pause instance %s failed as exception %s" %
                          (instance.name, e))
            raise exception.InstancePauseFailure(name=instance.name)

    def unpause(self, instance_name):
        """
        Unpause paused VM instance
        """
        instance, mach = self._get_machine(instance_name)
        if mach.state != self.vbm.constants.MachineState_Paused:
            raise exception.InstanceUnpauseRequired(name=instance.name)
        try:
            LOG.info('Unpausing the instance %s' % instance.name)
            with VboxSession(self.vbm, instance, False) as session:
                session.console.resume()
            instance = self._update_instance_state(instance, vbox_states.RUNNING)
            LOG.info('Complete unpausing the instance %s' % instance.name)
            return instance
        except Exception as e:
            self._update_instance_state(instance, vbox_states.ERROR)
            LOG.exception("Unpause instance %s failed as exception %s" %
                          (instance.name, e))
            raise exception.InstanceUnpauseFailure(name=instance.name)

    def hibernate(self, instance_name):
        """
        Hibernate the instance, the state of instance would go to SAVED
        """
        instance, mach = self._get_machine(instance_name)
        if mach.state not in [self.vbm.constants.MachineState_Running,
                              self.vbm.constants.MachineState_Paused]:
            raise exception.InstanceHibernateRequired(name=instance.name)
        try:
            LOG.info('Hibernating the instance %s' % instance.name)
            with VboxSession(self.vbm, instance, False) as session:
                progress = session.console.saveState()
                progress.waitForCompletion(-1)
            instance = self._update_instance_state(instance, vbox_states.SAVED)
            LOG.info('Complete hibernating the instance %s' % instance.name)
            return instance
        except Exception as e:
            self._update_instance_state(instance, vbox_states.ERROR)
            LOG.exception("Hibernate instance %s failed as exception %s" %
                          (instance.name, e))
            raise exception.InstanceHibernateFailure(name=instance.name)  
    
    def resize(self, instance_name, cpu_num, memory_mb,
               display_name=None, display_description=None):
        """
        Resize the instance, the state of instance should be powered off
        :param instance: instance object
        :param cpu_num: cpu core number
        :param memory_mb: memory size in mb
        :param display_name: display name to display the instance
        :param display_description: display description of the instance
        """
        instance, mach = self._get_machine(instance_name)
        if mach.state != self.vbm.constants.MachineState_PoweredOff:
            raise exception.InstanceResizeRequired(name=instance.name) 
        
        try:
            LOG.info('Resizing the instance %s' % instance.name)
            with VboxSession(self.vbm,instance) as session:
                mach = session.machine
                mach.CPUCount = cpu_num
                mach.memorySize = memory_mb
            config = instance.config
            config.update(dict(cpu_num=cpu_num, memory=memory_mb))
            if display_name: config.update(dict(display_name=display_name))
            if display_description: config.update(dict(display_description=display_description))
            config.save()
            LOG.info('Complete Resizing the instance %s' % instance.name)
            return instance
        except Exception as e:
            LOG.exception("Resizing instance %s failed as exception %s" %
                          (instance.name, e))
            raise exception.InstanceResizeFailure(name=instance.name)  
         
    
    def _reset_inactive_snapshots(self, instance, snapshot):
        """
        Persistent object instance and snapshot.
        :param instance: persistent object of django model object
        :param snapshot: persistent object of django model object
        """
        snapshot.is_current = True
        snapshot.save()
        
        snapshots = Snapshot.objects.filter(instance=instance, )\
                                    .exclude(id=snapshot.id)
        if snapshots:
            for snap in snapshots:
                snap.is_current = False
                snap.save()
                
        return snapshot

    
    def take_snapshot(self, instance, name, description=None):
        """
        take snapshot, here snapshot implicitly call saveSettings, so here not 
        using VBoxSession. 
        :param instance: instance object
        :param name: snapshot name
        :param description: snapshot description
        """
        instance, mach = self._get_machine(instance)
        if mach.state not in [self.vbm.constants.MachineState_PoweredOff,
                              self.vbm.constants.MachineState_Saved,
                              self.vbm.constants.MachineState_Running,
                              self.vbm.constants.MachineState_Paused]:
            raise exception.SnapshotTakeRequired(name=instance.name)
        
        permitSharing = False if mach.sessionState == self.vbm.constants.SessionState_Unlocked else True
        try:
            LOG.info("Snapshoting the instance %s" % instance.name)
            snap_obj = None
            # (yanyu), takeSnapshot would implicitly call saveSettings also 
            # it required write lock when the machine session state is unlocked
            with VboxSession(self.vbm, instance, False, permitSharing) as session:
                # for running snapshot please refer to http://www.virtualbox.org/ticket/9255
                # so here for running snapshot, 1. pause first 2. take snapshot 3. resume vm
                is_running = False
                if mach.state == self.vbm.constants.MachineState_Running:
                    is_running = True
                    session.console.pause()
                    
                progress = session.console.takeSnapshot(name, description)
                progress.waitForCompletion(-1)
                
                if is_running:
                    session.console.resume()
                
                mach = session.machine
                i_snapshot = mach.currentSnapshot
                if i_snapshot:
                    snap_obj = Snapshot(snapid=uuid.uuid4(), uuid=i_snapshot.id, 
                                        name=name, description=description, instance=instance)
                    snap_obj.save()
                    snap_obj.update(dict(snapid=constant.SNAP_TEMPLATE % snap_obj.id))
                    snap_obj.save()
                    
                    snap_obj = self._reset_inactive_snapshots(instance, snap_obj)
            
            primary_medium = self._get_primary_medium(instance) 
            instance.primary_medium_loc = primary_medium.location
            instance.save()       
            
            if snap_obj:
                snap_obj.update(location=primary_medium.parent.location)
                snap_obj.save()
            else:
                exception.SnapshotTakeFailure(name=instance.name)
            
            instance_medium = InstanceMedium(uuid=primary_medium.id,
                                             parent_uuid=primary_medium.parent.id \
                                             if primary_medium.parent else None,
                                             location=primary_medium.location,
                                             instance=instance)
            instance_medium.save()
            
            LOG.info("Complete snapshoting the instance %s" % instance.name)
            return snap_obj
        except Exception as e:
            LOG.exception("Snapshot instance %s failed as exception %s" % 
                          (instance.name, e))
            raise exception.SnapshotTakeFailure(name=instance.name)
            
    def _get_snapshot(self, snapshot):
        """
        Get the snapshot from database
        :param snapshot: snapshot object which provide snapid
        """
        snapid = snapshot.snapid
        snapshot = Snapshot.objects.get_or_none(snapid=snapid, )
        if not snapshot:
            raise exception.SnapshotNotFound(snapshot_id=snapid)

        return snapshot
            
    def restore_snapshot(self, snapshot):
        """
        Restore the current instance to snapshot, the restoreSnapshot implicitly
        the setting changed, so not using VBoxsession
        :param snapshot: snapshot which provide snapid
        """
        snapshot = self._get_snapshot(snapshot)
        instance, mach = self._get_machine(snapshot.instance.name)
        if mach.state == self.vbm.constants.MachineState_Running:
            raise exception.SnapshotRestoreInvalid(name=instance.name)
        
        try:
            LOG.info("Restoring the instance %s of snapshot %s" % 
                          (instance.name, snapshot.snapid))
            org_primary_medium = self._get_primary_medium(instance)
            org_primary_medium_uuid = org_primary_medium.id
            # (yanyu), restoreSnapshot would implicitly call saveSettings also 
            # it required write lock
            with VboxSession(self.vbm, instance, False, False) as session:
                i_snapshot = session.machine.findSnapshot(snapshot.uuid)
                
                # check this snapshot is the parent of current running medium
                if i_snapshot.getChildrenCount() > 0:
                    raise exception.SnapshotRestoreDirectParentInvalid(name=instance.name,
                                                                       snapshot_id=snapshot.snapid)
                
                progress = session.console.restoreSnapshot(i_snapshot)
                progress.waitForCompletion(-1)
                    
                snapshot = self._reset_inactive_snapshots(instance, snapshot)
            
            primary_medium = self._get_primary_medium(instance) 
            instance.primary_medium_loc = primary_medium.location
            instance.save()    
            
            org_instance_medium = InstanceMedium.objects.get_or_none(uuid=org_primary_medium_uuid)
            instance_medium = InstanceMedium(uuid=primary_medium.id,
                                             parent_uuid=org_instance_medium.parent_uuid,
                                             location=primary_medium.location,
                                             instance=instance)
            instance_medium.save()
            org_instance_medium.delete()
            
            LOG.info("Complete restoring the instance %s of snapshot %s" %
                     (instance.name, snapshot.snapid))
            return snapshot
        except exception.SnapshotRestoreDirectParentInvalid as srdpe:
            raise srdpe
        except Exception as e:
            LOG.exception("Restore the instance %s to snapshot %s. "
                          "Exception message %s."
                          % (instance.name, snapshot.snapid, e))
            raise exception.SnapshotResotreFailure(snapshot_id=snapshot.snapid,
                                                   name=instance.name)
            
    def delete_snapshot(self, snapshot):
        """
        Delete the snapshot
        :param snapshot: snapshot which provide snapid
        """
        snapshot = self._get_snapshot(snapshot)
        instance, _ = self._get_machine(snapshot.instance.name)
        org_instance_medium = InstanceMedium.objects.get(
                                                         location=snapshot.location)
        tail_mediums = InstanceMedium.objects.filter(
                                                 parent_uuid=org_instance_medium.uuid) 
        try:
            LOG.info("Deleting the snapshot %s of instance %s" %
                     (snapshot.snapid, instance.name))
            # (yanyu), deleteSnapshot would implicitly call saveSettings 
            with VboxSession(self.vbm, instance, False) as session:
                i_snapshot = session.machine.findSnapshot(snapshot.uuid)
                
                # (yanyu), need to check if current snapshot has two children
                # raise error message since it is invalid here
                if i_snapshot.getChildrenCount() > 1:
                    raise exception.SnapshotDeleteInvalid(snapshot_id=snapshot.snapid,
                                                          name=instance.name)
                progress = session.console.deleteSnapshot(i_snapshot.id)
                progress.waitForCompletion(-1)
                
                snapshot.delete()
                
                if session.machine.currentSnapshot:
                    snap_obj = Snapshot.objects.get_or_none(uuid=session.machine.currentSnapshot.id,
                                                    instance=instance)
                    self._reset_inactive_snapshots(instance, snap_obj)
            
            primary_medium = self._get_primary_medium(instance) 
            instance.primary_medium_loc = primary_medium.location
            instance.save()    
            
            # update the tail mediums
            for tail_medium in tail_mediums:
                tail_medium.update(parent_uuid=org_instance_medium.parent_uuid)
                tail_medium.save()
            org_instance_medium.delete()
            
            LOG.info("Complete deleting the snapshot %s of instance %s" %
                     (snapshot.snapid, instance.name))
        except exception.SnapshotDeleteInvalid as snap_invalid:
            raise snap_invalid             
        except Exception as e:
            LOG.exception("Delete snapshot %s fail for instance %s. "
                          "Exception message %s."
                          % (snapshot.snapid, instance.name, e))
            raise exception.SnapshotDeleteFailure(snapshot_id=snapshot.snapid,
                                                  name=instance.name)
    
    def delete_snapshot_and_children(self, instance, snapshot):
        """
        TODO(yanyu), not finished
        """
        try:
            with VboxSession(self.vbm, instance) as session:
                i_snapshot = session.machine.findSnapshot(snapshot.uuid)
                progress = session.console.deleteSnapshotAndAllChildren(i_snapshot.id)
                progress.waitForCompletion(-1)
        except Exception as e:
            LOG.exception("Delete snapshot and children fail with uuid %s for instance %s. "
                          "Exception message %s."
                          % (snapshot.uuid, instance.name, e))
            raise exception.Error("Delete snapshot and children fail with uuid %s for instance %s."
                          % (snapshot.uuid, instance.name))
    
    def allocate_volume(self,volume_ref):
        volume_path = os.path.join(self.volumepath,"%s.vdi" % volume_ref.name)
        volume = self.vbox.createHardDisk('VDI',volume_path)
        size = volume_ref.size * constant.TO_GB
        progress = volume.createBaseStorage(size,self.vbm.constants.MediumVariant_Standard)
        progress.waitForCompletion(-1)


    def attach_volume(self, instance,volume_ref):
        """Attach the disk at device_path to the instance"""
        mach = self.vbox.findMachine(instance.name)
        if not mach:
            raise exception.InstanceNotFound(instance_id=instance.name)
        
        storage_ctl = mach.getStorageControllerByName(constant.SATA)
        
        #(shankj):port不在数据库中累加，以免状态不一致，以api为准
        attachments = mach.getMediumAttachmentsOfController(constant.SATA)
        port = len(attachments) if attachments else 0
        
        if port >= storage_ctl.maxPortCount:
            raise exception.Error('volumes count of instance attached must less than SATA maxPortCount')
        
        if self.use_iscsi:
            iscsi_properties = self._get_iscsi_target_iqn(volume_ref)
            
            #强制 remove 
            try:
                medium = self.vbox.findMedium(volume_ref.name,self.vbm.constants.DeviceType_HardDisk)
                medium.close()
            except Exception as e:
                LOG.warning('force closing a medium, warning message is %s.' % e)
                
            with VboxSession(self.vbm,mach) as session:
                mach = session.machine
                hdd = self.vbox.createHardDisk('iSCSI',volume_ref.name)
                hdd.setProperty('TargetAddress',iscsi_properties['target_portal'])
                hdd.setProperty('TargetName',iscsi_properties["target_iqn"])
                mach.attachDevice(storage_ctl.name,port,constant.SOLT_MASTER,
                                    self.vbm.constants.DeviceType_HardDisk,hdd)
                    
        else:
            # ask volume server the path
            volume_path = os.path.join(self.volumepath,"%s.vdi" % volume_ref.name)
            
            # make sure the file existed
            if not os.path.exists(volume_path):
                LOG.debug('volume file is not existed for instance %s,going to create one.' % instance.name)
                self.allocate_volume(volume_ref)
            
            volume = self.vbox.openMedium(volume_path,self.vbm.constants.DeviceType_HardDisk, self.vbm.constants.AccessMode_ReadWrite,True)
            
            with VboxSession(self.vbm, instance) as session:
                session.machine.attachDevice(storage_ctl.name,port,constant.SOLT_MASTER,
                                             self.vbm.constants.DeviceType_HardDisk,volume)
            
        # update volume TODO
        # volume_ref.update({'status':V_STATE.IN_USE,
        #                     'instance':instance,
        #                     'attach_time':utils.utcnow(),
        #                     'port':port,
        #                     'device':constant.SOLT_MASTER})
        # volume_ref.save()
            
    def _get_iscsi_target_iqn(self,volume_ref):
        properties = {}

        location = volume_ref.iscsi_location
        
        if not location:
            raise exception.VolumeLocationMissing()


        (iscsi_target, _sep, iscsi_name) = location.partition(" ")

        iscsi_portal = iscsi_target.split(",")[0]

        properties['target_iqn'] = iscsi_name
        properties['target_portal'] = iscsi_portal    
        return properties
    
    def detach_volume(self, instance, volume_ref):
        """Detach the disk attached to the instance"""
        try:
            mach = self.vbox.findMachine(instance.name)
            
            #(shankj)如果是iscsi则使用名字即可，nfs需要location
            volume_loc = volume_ref.name if self.use_iscsi else \
                    os.path.join(self.volumepath,"%s.vdi" % volume_ref.name)
            
            medium = self.vbox.findMedium(volume_loc,self.vbm.constants.DeviceType_HardDisk)
            
            if medium:
                with VboxSession(self.vbm,mach) as session:
                    mach = session.machine
                    mach.detachDevice(constant.SATA,volume_ref.port,constant.SOLT_MASTER)
                
                #(shankj): 磁盘close必须要在machine session close之后，
                #不然medium和machine关联的状态不会更新，导致不能close
                medium.close()
            #TODO
            # volume_ref.update({'port':-1,'instance':None,'status':V_STATE.AVAILABLE})
            # volume_ref.save()
        except Exception as e:
            LOG.exception(e)

    
    def get_block_migration_port(self):
        """
        Get block migration port in current host.
        """
        for i_dict in self.block_migration_port:
            if not i_dict['used']:
                i_dict['used'] = True
                return i_dict['port']
        
        raise exception.BlockMigrationPortRequired(host=self.host)
    
    def release_block_migration_port(self, port):
        """
        Release block migration port
        """
        for i_dict in self.block_migration_port:
            if i_dict['port'] == port:
                i_dict['used'] = False
                break

    def block_migration_receive_images(self, instance, src_host, port):
        """
        If the live migration is doing block migration, we need to receive the
        image sent from source host, and save in destination host.
        :param instance: virtual machine
        :param port: receiving port
        """
        instance = self._get_instance_in_host(instance, src_host)
        vmref = self._get_vm_from_name(instance.name)
        if vmref: 
            raise exception.InstanceExists(name=instance.name)
        if instance.vm_state != vbox_states.SHUTDOWN:
            raise exception.BlockMigrationRequired(name=instance.name)
            
        image_path = os.path.join(instance.mount_point, instance.name)
        try:
            if not os.path.exists(image_path):
                os.mkdir(image_path)
            
            def _receive_images(image_path, instance, port):
                server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                server.bind((self.host, port))
                server.listen(1)
                server.settimeout(20)
                tar_path = os.path.join(instance.mount_point, '%s.tar' % instance.name)
                try:
                    conn, addr = server.accept()
                    LOG.info("Block migration listen on %s, port %s for receiving." % addr)
                    server.settimeout(None)
                    LOG.info("Block migration receiving file from host %s." % src_host)
                    head_data = conn.recv(8)
                    file_size = struct.unpack('l', head_data)[0]
                    with file(tar_path, 'wb') as fobj:
                        utils.recv_file(file_size, conn, fobj, True)
                    tarfile.TarFile(tar_path).extractall(instance.mount_point)
                    conn.send('ok')
                    conn.close()
                    LOG.info("Complate block migration receiving file from host %s." % src_host)
                except Exception as e:
                    LOG.exception("Block migration of receiving instance %s failed, %s" % 
                          (instance.name, e))
                    raise exception.BlockMigrationReceiveFailure(name=instance.name)
                finally:
                    os.remove(tar_path)
                    server.shutdown(socket.SHUT_RDWR)
                    server.close()
            self.pool.spawn(_receive_images, image_path, instance, port)
            eventlet.greenthread.sleep(1)
        except Exception as e:
            LOG.exception("Block migration of receiving instance %s failed, %s" % 
                          (instance.name, e))
            raise exception.BlockMigrationReceiveFailure(name=instance.name)
    
    def block_migration_send_images(self, instance, dst_host, port):
        """
        If choose block migration, only migrate the primary difference image and
        the secondary store image both are stored in source host.
        :param instance: instance object
        :param instance: destination host which would receive the images 
        :param port: port which would receive the images
        """
        instance, _ = self._get_machine(instance.name)
        tar_path = None
        try:
            LOG.info("Block migration sending file to host %s." % dst_host)
            
            dest = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            dest.connect((dst_host, port))
            
            tar_path = shutil.make_archive(os.path.join(instance.mount_pointh, instance.name),
                                'tar', instance.mount_point, instance.name)
            
            file_size = os.path.getsize(tar_path)
            head_data = struct.pack('l', file_size)
            dest.send(head_data)
            
            with file(tar_path, 'rb') as fobj:
                utils.send_file(file_size, fobj, dest, True)
            
            if dest.recv(2) != 'ok':
                raise exception.BlockMigrationSendFailure(name=instance.name)
            dest.close()
            LOG.info("Complete block migration sending file to host %s." % dst_host)
        except Exception as e:
            LOG.exception("Block migration of sending instance %s failed, %s" % 
                          (instance.name, e))
            raise exception.BlockMigrationSendFailure(name=instance.name)
        finally:
            if tar_path: os.remove(tar_path)
        
    def block_migration_pre_open_instance(self, instance, src_host):
        """
        Before doing block migration, we need to create a new virtual machine in 
        destination host.
        :param instance: virtual machine
        :param src_host: source host
        # TODO, need to add network(yanyu)
        """
        instance = self._get_instance_in_host(instance, src_host)
        vmref = self._get_vm_from_name(instance.name)
        if vmref: 
            raise exception.InstanceExists(name=instance.name)
        
        try:
            LOG.info("Block migration opening medium of instance %s." 
                      % instance.name)
            self._cache_image_hdd(instance.image,instance.mount_point)
            self.vbox.openMedium(instance.primary_medium_loc,
                                 self.vbm.constants.DeviceType_HardDisk,
                                 self.vbm.constants.AccessMode_ReadOnly,
                                 False)
            LOG.info("Complete block migration opening medium of instance %s." 
                      % instance.name)
        except Exception as e:
            LOG.exception("Block migration open base or "
                          "primary medium for instance %s failed, %s" % 
                          (instance.name, e))
            raise exception.BlockMigrationMediumFailure(name=instance.name)
        
        try:
            LOG.info("Block migration opening machine of instance %s." 
                      % instance.name)
            setting_path = os.path.join(self.instancepath, instance.name,
                                        '%s.vbox' % instance.name)
            mach = self.vbox.openMachine(setting_path)
            self.vbox.registerMachine(mach)
            LOG.info("Complete block migration opening machine of instance %s." 
                      % instance.name)
        except Exception as e:
            LOG.exception("Block migration open machine for instance %s failed, %s" %
                          (instance.name, e))
            raise exception.BlockMigrationOpenMachineFailure(name=instance.name)
        
            
    def block_migration_post_destroy_instance(self, instance, dest_host):
        """
        After doing block migration, we need to delete the virtual machine in 
        source host.
        :param instance: instance object
        :param dest_host: destination host point to after the instance deleted
                            in source host 
        """
        instance, _ = self._get_machine(instance.name)
        LOG.info("Block migration destroying machine of instance %s."
                  % instance.name)
        self.destroy(instance, True, False)
        # not truely destroy in db, apply back fixedip and set the correct pointing host
        instance.host = dest_host
#        instance.vm_state = vbox_states.SHUTDOWN
        instance.save()
        LOG.info("Complete block migration destroying machine of instance %s."
                  % instance.name)
        return instance
    
    def get_teleport_port(self):
        """
        Get teleport port in current host.
        """
        for i_dict in self.teleport_port:
            if not i_dict['used']:
                i_dict['used'] = True
                return i_dict['port']
        
        raise exception.TeleportPortRequired(host=self.host)
    
    def release_teleport_port(self, port):
        for i_dict in self.teleport_port:
            if i_dict['port'] == port:
                i_dict['used'] = False
                break
    
    def _get_primary_medium(self, instance):
        """
        Get the primary medium, since for some vm may take snapshot and 
        the primary medium is changed
        :param instance: instance object
        """
        _, mach = self._get_machine(instance.name)
        medium_attachs = mach.getMediumAttachmentsOfController(constant.SATA)
        for medium_attach in medium_attachs:
            if medium_attach.device == constant.SOLT_MASTER and \
                medium_attach.port == constant.DEFAULT_PORT and \
                medium_attach.type == self.vbm.constants.DeviceType_HardDisk:
                return medium_attach.medium
        return None

    def pre_teleport(self, instance, src_host, port, password=None):
        """
        Before doing live migration, we need to create a new virtual machine in 
        destination host and setting the teleport parameters.
        :param instance: virtual machine
        :param src_host: which source host would do the teleporter
        :param port: teleport port
        :param passwor: teleport password
        
        # not finished yet, yanyu
        """
        instance = self._get_instance_in_host(instance, src_host)
        
        if instance.vm_state not in [vbox_states.RUNNING, vbox_states.PAUSED]:
            raise exception.TeleportInstanceStateRequired(name=instance.name)
        
        vmref = self._get_vm_from_name(instance.name)
        if vmref: 
            raise exception.InstanceExists(name=instance.name)
        
        if not os.path.exists(instance.primary_medium_loc):
            raise exception.TeleportShareStorageRequired(name=instance.name)
        
        # check the primary medium, set the parent uuid(yanyu)
        LOG.info("Pre teleport opening medium of instance %s." % instance.name)
        try:
            instance_medium = InstanceMedium.objects.get_or_none(
                                                     location=instance.primary_medium_loc)
            self._cache_image_hdd(instance.image,instance.mount_point)
            self._open_medium(instance_medium)
        except Exception as e:
            LOG.exception("Pre teleport open base or primary medium for instance %s failed, %s" %
                          (instance.name, e))
            raise exception.TeleportMediumFailure(name=instance.name)
        LOG.info("Complete pre teleport opening medium of instance %s." % instance.name)
        
        try:
            LOG.info("Pre teleport opening machine of instance %s." % instance.name)
            setting_path = os.path.join(self.instancepath, instance.name,
                                        '%s.vbox' % instance.name)
            mach = self.vbox.openMachine(setting_path)
            self.vbox.registerMachine(mach)
            
            with VboxSession(self.vbm, instance) as session:
                session.machine.teleporterEnabled = True
                #yanyu, if this address set invalid, it would cause VERR_NET_ADDRESS_NOT_AVAILABLE
                session.machine.teleporterAddress = self.host
                session.machine.teleporterPort = port
                session.machine.teleporterPassword = password
                #yanyu, rdp address, if this address put in the post teleport, it would not work
                session.machine.VRDEServer.setVRDEProperty('TCP/Address',self.host)
            LOG.info("Complete pre teleport opening machine of instance %s." % instance.name)
        except Exception as e:
            LOG.exception("Pre teleport register instance %s failed, %s" %
                          (instance.name, e))
            raise exception.TeleportRegisterFailure(name=instance.name)
        
    def pre_teleport_start_instance(self, instance, runtype='headless', waiting=300):
        """
        :param instance: instance object
        :param runtype: runtype of vm, default to 'headless'
        """
        try:
            LOG.info('Pre teleport starting the instance %s with run type %s' % 
                      (instance.name, runtype))
            
            def _teleport_start_instance(uuid, runtype):
                #yanyu, this need a single process to start
                os.system("vboxmanage startvm " + uuid + " --type " + runtype)
                
            process = Process(target=_teleport_start_instance, args=(instance.uuid,
                                                                     runtype,))
            process.start()
            process.join(waiting)
            if process.is_alive():
                process.terminate()
                raise exception.InstanceStartFailure(name=instance.name)
            
            LOG.info('Complete Pre teleport starting the instance %s' % instance.name)
            return 'ok'
        except Exception as e:
            LOG.exception('Pre teleport start instance %s failed as exception %s' % 
                          (instance.name, e))
            raise exception.InstanceStartFailure(name=instance.name)
    
    def get_teleporting_state(self, instance):
        """
        :param instance: instance object
        Check the vm is state is TeleportingIn
        """
        machine = self.vbox.findMachine(instance.name)
        if machine.state == self.vbm.constants.MachineState_TeleportingIn:
            return True
        return False
    
    def post_disable_teleport_status(self, instance):
        """
        :param instance: instance object
        """
        LOG.info("Post disable teleport for instance %s." % instance.name)
        try:
            with VboxSession(self.vbm, instance) as session:
                session.machine.teleporterEnabled = False
        except Exception as e:
            LOG.exception("Post disable teleport status error %s." % e)
        LOG.info("Complete post disable teleport for instance %s." % instance.name)
        
    def post_teleport_destroy_instance(self, instance, dest_host):
        """
        Post destroy virtual machin in source host and update the instance object
        :param instance: instance object
        :param dest_host: after virtual machine destroy, the destination host point to
        """
        instance, mach = self._get_machine(instance.name)
        LOG.info("Post teleport destroying machine of instance %s." % instance.name)
        
        self._wait_for_unlock(mach)
        
#        os.system("vboxmanage unregistervm " + instance.uuid)
        # yanyu, can not use destroy since it would raise issue session unlocked
        self.destroy(instance, False)
        # not truely destroy in db
        instance.host = dest_host

#        instance.vm_state = vbox_states.RUNNING
        instance.save()
        LOG.info("Complete post teleport destroying machine of instance %s." % instance.name)
        return instance
    
    def teleport_apply_network(self, instance):
        """
        Apply network for the destination host
        """
        LOG.info("Teleport apply network for instance %s." % instance.name)
        self.bind_fixed_ip(instance)
        self.fwdriver.apply_instance_filter(instance)
        LOG.info("Complete teleport apply network for instance %s." % instance.name)
    
    def teleport_port_forward(self, instance):
        """
        Apply port forward in destination host
        """
        LOG.info("Teleport apply redirect for instance %s." % instance.name)
        self.apply_redirect(instance)
        LOG.info("Complete teleport apply redirect for instance %s." % instance.name)
    
    def teleport_paused(self, instance):
        """
        Check the vm whether is running or not, if running, paused it first.
        :param instance: instance object
        #yanyu, should paused first, other there would be issue like 'gpm' 
        """
        instance, mach = self._get_machine(instance.name)
        if mach.state not in [self.vbm.constants.MachineState_Running,
                              self.vbm.constants.MachineState_Paused]:
            raise exception.TeleportInstanceStateRequired(name=instance.name)
        is_paused = True
        if mach.state == self.vbm.constants.MachineState_Running:
            is_paused = False
            self.pause(instance)
        return is_paused
        
    def teleport(self, instance, dest, port, password=None):
        """Spawning live_migration operation for distributing high-load.

        :param ctxt: security context
        :param instance_ref:
            thor.db.sqlalchemy.models.Instance object
            instance object that is migrated.
        :param dest: destination host

        """
        instance, mach = self._get_machine(instance.name)

        if mach.state not in [self.vbm.constants.MachineState_Running,
                              self.vbm.constants.MachineState_Paused]:
            raise exception.TeleportInstanceStateRequired(name=instance.name)
        
        try:
            LOG.info("Teleport instance %s to host %s." % 
                      (instance.name, dest))
            
            with VboxSession(self.vbm,mach,False) as session:
                progress = session.console.teleport(dest, port, password, 250)
                progress.waitForCompletion(-1)
                
            LOG.info("Complete teleport instance %s to host %s." % 
                      (instance.name, dest))
        except Exception as e:
            LOG.exception(e)
            raise exception.TeleportingFailure(name=instance.name,
                                               src_host = self.host,
                                               dest_host = dest)

    # def allocate_fixed_ip(self,instance, address=None):
    #     if address:
    #         fixedip = Fixedip.objects.filter(address=address,leased=False,reserved=False)
    #     else:
    #         fixedip = Fixedip.objects.filter(leased=False,reserved=False)
    #     if not fixedip:
    #         raise exception.FixedIpNotFound()
    #     fixedip[0].instance = instance
    #     fixedip[0].leased = True
    #     fixedip[0].save()
        
    # def unallocate_fixed_ip(self,instance):
    #     fixedip = Fixedip.objects.filter(instance=instance)
    #     for ip in fixedip:
    #         ip.instance = None
    #         ip.leased = False
    #         ip.save()
        
    # def re_apply_redirect_fixed_ip(self, instance, address):
    #     """
    #     re-apply redirect fixed ip for instance
    #     """
    #     instance, mach = self._get_machine(instance)
    #
    #     if mach.state == self.vbm.constants.MachineState_Running:
    #         self.unbind_fixed_ip(instance)
    #         self.fwdriver.unfilter_instance(instance)
    #     self.unallocate_fixed_ip(instance)
    #
    #     self.apply_redirect(instance)
    #
    #     fixedip = Fixedip.objects.filter(address=address,
    #                                      leased=False,
    #                                      reserved=False)
    #     if not fixedip:
    #         raise exception.FixedIpNotFound()
    #     fixedip[0].instance = instance
    #     fixedip[0].leased = True
    #     fixedip[0].save()
    #
    #     if mach.state == self.vbm.constants.MachineState_Running:
    #         self.bind_fixed_ip(instance)
    #         self.fwdriver.apply_instance_filter(instance)
    #         greenthread.sleep(2)
    #         self.apply_redirect(instance)
    #
    #
    # def unbind_fixed_ip(self, instance,interface='eth0'):
    #     """
    #         解除绑定ip到宿主机
    #         在虚拟机停止时调用
    #     """
    #     ips = linux_net.get_binded_ips(interface)
    #
    #     for ip in instance.fixedip_set.filter(deleted=0):
    #         address = "%s/24" % ip.address
    #         if address in ips:
    #             utils.execute('ip','addr','del',address,'brd','+','dev',interface,run_as_root=True)
    #
    #
    # def bind_fixed_ip(self, instance,interface='eth0'):
    #     """
    #         绑定ip到宿主机，已绑定则跳过
    #         在虚拟机启动时调用，port forwarding和iptables规则在创建时已经设置好
    #     """
    #     ips = linux_net.get_binded_ips(interface)
    #
    #     for ip in instance.fixedip_set.filter(deleted=0):
    #         address = "%s/24" % ip.address
    #         if address not in ips:
    #             #子网掩码使用原来网络的默认值
    #             utils.execute('ip','addr','add',address,'brd','+','dev',interface,run_as_root=True)
        
        
    def refresh_security_group_rules(self, instance):
        """This method is called after a change to security groups.

        All security groups and their associated rules live in the datastore,
        and calling this method should apply the updated rules to instances
        running the specified security group.

        An error should be raised if the operation cannot complete.
        
        rule = name,proto,hostip,hostport,guestip,guestport
        name is rule.id
        """
        self.apply_redirect(instance)
        
        #apply instance filter
        self.fwdriver.refresh_security_group_rules(instance)

    def apply_redirect(self,instance):
        '''
        设定虚拟机redirect规则
        1.加入未加入的规则
        2.删除不在使用的规则
        
        如果新的是[1,2,4,5],旧的是[1,2,3],则需要移除的是[3],增加的是[4,5]
        '''
        groups = instance.sercurity_group.filter(deleted=0)
        fixedips = instance.fixedip_set.filter(deleted=0)
        rules = []
        for group in groups:
            for r in group.securitygroupingressrule_set.filter(deleted=0):
                if r.protocol in ['tcp','udp']:
                    for port in range(r.from_port,r.to_port+1):
                        for fixedip in fixedips:
                            rules.append(redirect(self.vbm.constants,"rule_%s" % port,r.protocol,fixedip.address,port,port))
        
        LOG.info('found %s redirects for instance %s' % (len(rules),instance.name))
        with VboxSession(self.vbm,instance) as session:
            mach = session.machine
            for i in range(instance.config.adapter_count):
                nd = mach.getNetworkAdapter(i)
                reds = nd.natDriver.getRedirects()
                guestip = "10.0.%s.15" % (i+2)
                for rule in rules:
                    if not rule.get_s(guestip) in reds:
                        LOG.info('add redirect (%s) to instance %s ' % (rule.get_s(guestip),instance.name))
                        nd.natDriver.addRedirect(rule.name,rule.proto,rule.hostip,rule.hostport,
                                                 guestip,rule.guestport)
                
                newred = [r.get_s(guestip) for r in rules]
                for rules in filter(lambda r: r not in newred,reds):
                    LOG.info('remove redirect (%s) from instance %s ' % (rules,instance.name))
                    nd.natDriver.removeRedirect(rules.split(',')[0])  #第一个元素是规则名
    

    
    def get_instances_info(self):
        """
        Only running machine can have warning message, here return the warning
        machine.
        The results are json dump like:
        [{'id':123, 'name':'i-00000123', 'down':true}]
        [{'id':123, 'name':'i-00000123', 'inconsistent':true}]
        [{'id':123, 'name':'i-00000123', 'cpu':59, 'mem':45, 'disk':34}]
        [{'id':123, 'name':'i-00000123', 'inconsistent':true, 'cpu':59, 'mem':45, 'disk':34}]
        """
        host = Server.objects.get(ip=self.host)
        instances = Instance.objects.filter(host=host)
        instances_info = []
        if instances:
            for inst in instances:
                inst_info = {'id':inst.id, 'name':inst.name}
                disk_size = 0.0
                used_disk_size = 0.0
                mach_state = None
                stats = None
                try:
                    with VboxSession(self.vbm, inst) as session:
                        mach_state = session.machine.state
                        if mach_state == self.vbm.constants.MachineState_Running:
                            session.console.guest.statisticsUpdateInterval = 2
                            stats = session.console.guest.internalGetStatistics()
                            for attachs in session.machine.getMediumAttachments():
                                if attachs and attachs.medium:
                                    if  attachs.medium.deviceType == self.vbm.constants.DeviceType_HardDisk:
                                        disk_size += attachs.medium.logicalSize
                                        used_disk_size += attachs.medium.size
                except:
                    inst_info.update({'down':True})
                    instances_info.append(inst_info)
                    continue
                
                if not ((inst.vm_state == vbox_states.PAUSED and mach_state == self.vbm.constants.MachineState_Paused) \
                    or (inst.vm_state == vbox_states.SAVED and mach_state == self.vbm.constants.MachineState_Saved) \
                    or (inst.vm_state == vbox_states.RUNNING and mach_state == self.vbm.constants.MachineState_Running) \
                    or (inst.vm_state == vbox_states.SHUTDOWN and mach_state == self.vbm.constants.MachineState_PoweredOff)):
                    inst_info.update({'inconsistent':True})
                
                if stats:
                    cpu_percent = 100-stats[2] if 0 != stats[1] else 0
                    mem_percent = (stats[3] - stats[4])*1.0/stats[3] if stats[3] else 0
                    disk_percent = 0
                    if disk_size > 1:
                        disk_percent = used_disk_size/disk_size
                    inst_info.update({'cpu':cpu_percent,
                                      'mem':mem_percent*100,
                                      'disk':disk_percent*100})
                instances_info.append(inst_info)
        
        return json.dumps(instances_info)
    
    def get_host_info(self):
        '''返回cpu,memory,disk使用率'''
        pass
                        
if __name__ == '__main__':
     print utils.hasSoundCard()

